package com.taglab.chic.taglib;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.http.HttpServletRequest;

import java.io.File;
import java.util.concurrent.TimeUnit;

import com.taglab.chic.util.Computable;
import com.taglab.chic.util.UrlUtils;
import com.taglab.chic.util.ExpiringMemoizer;

/**
 * <p>
 *   Appends a versioning attribute to a URL, based on the last modified date of the underlying resource.
 * </p>
 *
 * <p>
 *   For example, <code>&lt;chic:vurl value="/myurl/myresource"&gt;</code> will be output as
 * <code>/myurl/myresource?v=0903271003</code>.
 * </p>
 *
 * <p>
 *   The tag is primarily used to version resources which are aggressively cached by browsers, such as stylesheets. The
 * tag also supports scopes and variables in a similar way to <code>&lt;c:url&gt;</code>.
 * </p>
 *
 * @author Damien Papworth
 */
public class VersionedUrlTag extends TagSupport {
  private static final Logger logger = LoggerFactory.getLogger(VersionedUrlTag.class);

  private static final long serialVersionUID = -6568799824038190759L;

  private static final String REQUEST = "request";
  private static final String SESSION = "session";
  private static final String APPLICATION = "application";

  private final long expiration = 5 * 60; // 5 minute expiration
  private final TimeUnit expirationUnit = TimeUnit.SECONDS;

  private final Computable<String, String> c = new Computable<String, String>() {

    public String compute(String contextFile) {
      File localFile = new File(pageContext.getServletContext().getRealPath(contextFile));
      if(localFile.exists()) {
        return UrlUtils.createVersionString(localFile);
      } else {
        logger.error("Unable to find {}", localFile.getAbsolutePath());
        return null;
      }
    }

  };

  private final Computable<String, String> urlVersioning = new ExpiringMemoizer<String, String>(c, expiration, expirationUnit);

  // Tag attributes
  private String value;
  private String var;
  private int scope;

  public VersionedUrlTag() {
    super();
    init();
  }

  private void init() {
    value = null;
    var = null;
    scope = PageContext.PAGE_SCOPE;
  }

  public void setValue(String value) {
    this.value = value;
  }

  public void setVar(String var) {
    this.var = var;
  }

  public void setScope(String scopeString) {
    scope = PageContext.PAGE_SCOPE;

    // Translate scope to PageContext equivalent
    if (REQUEST.equalsIgnoreCase(scopeString))
	    scope = PageContext.REQUEST_SCOPE;
	  else if (SESSION.equalsIgnoreCase(scopeString))
	    scope = PageContext.SESSION_SCOPE;
	  else if (APPLICATION.equalsIgnoreCase(scopeString))
	    scope = PageContext.APPLICATION_SCOPE;
  }

  public void release() {
    // Release resources
	  init();
  }

  public int doEndTag() throws JspException {
    // By default, add no versioning
    String versionedUrl = value;

    String contextFile = UrlUtils.getContextFile(pageContext.getServletContext(), (HttpServletRequest) pageContext.getRequest(), value);
    if(contextFile != null) {
      try {
        String version = urlVersioning.compute(contextFile);
        if(version != null) {
          versionedUrl = appendVersioning(value, version);
        } else {
          logger.warn("Unable to version {}", value);
        }
      } catch (InterruptedException ie) {
        logger.error("Versioning interrupted", ie);
      }
    }

    if (var != null) {
      // Add variable to context
      pageContext.setAttribute(var, versionedUrl, scope);
    } else {
      try {
        // Output value
        pageContext.getOut().print(versionedUrl);
      } catch (java.io.IOException ex) {
        throw new JspTagException(ex.toString(), ex);
      }
    }

    return EVAL_PAGE;
  }

  protected String appendVersioning(String url, String version) {
    // TODO Change versioning parameter to avoid clashes with other parameters?

    // Build versioning parameter for query string
    StringBuffer versioningParam = new StringBuffer("v=");
    versioningParam.append(version);

    StringBuffer workingUrl = new StringBuffer(url);
    int questionMark = workingUrl.indexOf("?");
    if (questionMark == -1) {
      // Append new query string containing versioning
      workingUrl.append("?");
      workingUrl.append(versioningParam);
    } else {
      // Insert parameter into existing query string
      versioningParam.append("&");
      workingUrl.insert(questionMark + 1, versioningParam);
    }
    return workingUrl.toString();
  }

}
